/*
 * Copyright (c) Huawei Technologies Co., Ltd. 2021-2022. All rights reserved.
 *
 */

import { DurationEvent, MetricLogType, TaskDetails } from '@ohos/hvigor';
import { SpawnSyncOptions } from 'child_process';
import * as fse from 'fs-extra';
import path from 'path';

import { NodeCommandBuilder } from '../builder/node-command-builder.js';
import { BuildArtifactConst } from '../const/build-directory-const.js';
import { CommonConst } from '../const/common-const.js';
import { AotCompileModeEnum } from '../enum/aot-compile-mode-enum.js';
import { CodeType } from '../enum/code-type-enum.js';
import { LegacyAbilityModel } from '../model/ability/legacy-ability-model.js';
import { LoaderEnv } from '../options/loader-env.js';
import { OhosLogger } from '../utils/log/ohos-logger.js';
import { ProcessUtils } from '../utils/process-utils.js';

import { AbstractCompileNode } from './abstract/abstract-compile-node.js';
import { ModuleTargetData } from './data/hap-task-target-data.js';
import { TargetTaskService } from './service/target-task-service.js';
import { CompileResource } from './compile-resource.js';
import { GenerateLoaderJson } from './generate-loader-json.js';

/**
 * 调用loader编译新模型的js/ets源码
 *
 * @since 2022/1/10
 */
export class CompileNode extends AbstractCompileNode {
  private _log: OhosLogger = OhosLogger.getLogger(CompileNode.name);
  private readonly _webpackPath = './node_modules/webpack/bin/webpack.js';
  private readonly webpackConfig: string;

  constructor(taskService: TargetTaskService, codeType: CodeType, taskDetails: TaskDetails) {
    super(taskService, codeType, taskDetails);
    this.webpackConfig = this.codeType === CodeType.ETS ? CommonConst.ETS_WEBPACK_FILE : CommonConst.ACE_RICH_WEBPACK_FILE;
  }

  initTaskDepends() {
    this.declareDependsList(GenerateLoaderJson.name, CompileResource.name);
  }

  protected async beforeTask(): Promise<void> {
    await super.beforeTask();
    if (this.targetService.getAnBuildMode() !== AotCompileModeEnum.AOT_PARTIAL) {
      fse.removeSync(this.anBuildOutputPath);
    }
  }

  protected async doTaskAction() {
    const subTaskName = 'generate compile-ArkTs-or-JS-component command';
    const subDurationEvent = this.durationEvent.createSubEvent(subTaskName, '');
    subDurationEvent.start();

    this.validateModuleJson(this._log);
    await this.doRealLoaderCompile(
      this.generateLoaderEnv(),
      (targetData: ModuleTargetData) => {
        this.moveReleaseMap(targetData, this._log);
      },
      [this.targetData],
      subDurationEvent,
      [0, 1],
    );
  }

  protected async doRealLoaderCompile(
    cmdEnv: LoaderEnv,
    callback: Function,
    callbackInput: unknown[],
    subDurationEvent: DurationEvent,
    targetWorkers?: number[],
  ): Promise<void> {
    cmdEnv.runtimeOS = this.sdkInfo.isOhos ? CommonConst.OPEN_HARMONY : CommonConst.HARMONY_OS;
    cmdEnv.sdkInfo =
      `${this.sdkInfo.isOhos}:${this.sdkInfo.getSdkVersion()}:` + `${this.sdkInfo.getEtsComponentVersion()}:${this.sdkInfo.getEtsComponentReleaseType()}`;
    fse.ensureDirSync(cmdEnv.cachePath);
    fse.ensureDirSync(cmdEnv.aceModuleBuild);
    const nodeCommandBuilder = this.createNodeCommandBuilder();

    this._log._printDebugCommand('NodeEnv', cmdEnv);
    this._log._printDebugCommand(`${this.codeType.toUpperCase()}-loader`, nodeCommandBuilder.build());

    const inputData = this.getInputData(cmdEnv, nodeCommandBuilder.build());

    subDurationEvent.stop();
    subDurationEvent.setLog(subDurationEvent.getName(), MetricLogType.INFO);
    const submitTaskName = 'submit compile-ArkTs-or-JS-component task to work pool';
    const subToolDurationEvent = this.durationEvent.createSubEvent(submitTaskName, '');
    subToolDurationEvent.start();

    await new ProcessUtils(inputData.moduleName, inputData.taskName).submitExecutionWithoutReturn(
      this,
      this.getWorkerPool(),
      inputData,
      callback,
      callbackInput,
      subToolDurationEvent,
      targetWorkers,
    );

    subToolDurationEvent.stop();
    subToolDurationEvent.setLog(submitTaskName, MetricLogType.INFO);
  }

  protected createNodeCommandBuilder() {
    const nodeNV: RegExpExecArray | null = /[0-9]+/.exec(process.version);
    const nodeV: number = nodeNV ? Number(nodeNV[0]) : -1;

    const nodeCommandBuilder: NodeCommandBuilder = new NodeCommandBuilder(true)
      .addOpenSslLegacyOption(this.codeType === CodeType.JS && 17 <= nodeV)
      .addWebpackPath(this._webpackPath)
      .addWebpackConfig(this.webpackConfig)
      .addBuildMode(this.targetService.isDebug());

    if (this.isArkModule) {
      nodeCommandBuilder.addCompilerType('ark');
    }
    return nodeCommandBuilder;
  }

  protected getInputData(cmdEnv: NodeJS.ProcessEnv, commandLine: string[]) {
    const options: SpawnSyncOptions = {
      cwd: this.codeType === CodeType.ETS ? this.sdkInfo.getEtsLoader() : this.sdkInfo.getJsLoader(),
      env: cmdEnv,
      windowsHide: true,
    };

    return {
      moduleName: this.moduleName,
      taskName: this.name,
      commandLine: commandLine,
      commandOptions: options,
    };
  }

  private generateLoaderEnv(): LoaderEnv {
    return {
      ...this.commonOption,
      appResource: this.resourceTable,
      rawFileResource: this.rawFileResource,
      aceModuleBuild: this.aceModuleBuild,
      aceModuleRoot: this.sourcePath!,
      cachePath: this.getTaskTempDir(this.targetData),
      aceProfilePath: this.resProfileDir,
      aceModuleJsonPath: this.aceModuleJsonPath,
      aceSuperVisualPath: this.aceSuperVisualPath,
      aceBuildJson: path.resolve(this.aceBuildJsonDir, BuildArtifactConst.LOADER_JSON),
      AnBuildOutPut: path.resolve(this.anBuildOutputPath, AotCompileModeEnum.ARM64_V8A),
      AnBuildMode: this.targetService.getAnBuildMode(),
      apPath: this.targetService.getApAbsolutePath(),
    };
  }

  /**
   * 检查是否存在js模块依赖ets lib的情况
   *
   * @private
   */
  protected checkLocalDependency(abilityObjs: LegacyAbilityModel[], log: OhosLogger) {
    if (this.codeType !== CodeType.JS) {
      return;
    }
    for (const abilityObj of abilityObjs) {
      if (abilityObj.getSrcLanguage() !== this.codeType) {
        return;
      }
    }
    const harDependencies = this.service.getHarDependencies();
    for (const harDependency of harDependencies) {
      if (harDependency.getDependencyMainFilePath().endsWith('.ets')) {
        log.printErrorExit(
          'JS_MODULE_RELY_ETS_LIB',
          [this.moduleName, harDependency.getDependencyName(), harDependency.isModuleDependency() ? this.moduleModel.getOhPackageJson5Path() : this.projectModel.getOhPackageJson5Path()],
          [[this.moduleName]]
        );
      }
    }
  }
}
